unit InterfazSerial;

interface

uses
  Windows,
  SysUtils,
  AdPort,
  OoMisc,
  AdSelCom,
  ExtCtrls,
  IntList,
  Contnrs,
  Math,
  Dialogs,
  Classes;


type
  TInterfaz = Class;

  TValue = Class(TObject)
    Timestamp: TDateTime;  // RETRIEVE IN DD, MM, YY, HH, II, SS, MS
    Tick: LongWord;    // DOUBLE WORD
    Value: Word;
    constructor Create(V: word);
  End;

  TSensor = Class(TComponent)
    private
    Interfaz: TInterfaz;
    DataFile: string;
    f: TextFile;
    Value: integer;
    Index: integer;
    published
    Burst: TObjectList;
    Values: TObjectList;
    Timer: TTimer;
    constructor Create;
    procedure SetTimer(Interval: Word);
    procedure ClearTimer;
    procedure OnTimer(Sender: TObject);
    procedure SaveToFile;
    function GetSensor: Word;
    End;

  TInterfaz = Class(TComponent)
    private
    Sensores: array[0..7] of TSensor;
    SensoresAskList: TIntList;
    curSensor: Word;
    public
    Com : TApdComPort;
    Buffer: TIntList;
    published
    constructor Create(Port:integer);
    procedure OnTriggerAvail(CP: TObject; Count: Word);
    procedure Close;
    function GetBufferFirst: word;
    procedure Call;
    procedure TalkMotor(Motors: byte);
    procedure MotorOn;
    procedure MotorOff;
    procedure MotorInverse;
    procedure MotorThisWay;
    procedure MotorThatWay;
    procedure MotorCoast;
    procedure MotorPower(Power: byte);
    procedure AllMotorsOff;
    procedure TalkPAP(PAP: Byte);
    procedure PAPSpeed(Speed: Byte);
    procedure PAPSteps(Steps: Word);
    procedure ServoPos(Pos: Byte);
    procedure TalkSensor(Sensor: Byte);
    function GetSensor: Word;
    procedure SensorBurst(Sensors: Byte; Slow: Byte);
    procedure StopBurst;
    function GetBurstValue: Word;
    function GetLastBurstValue: Word;
//    function GetBurstTimeStamp(Sensor: Byte): TDateTime;
    function GetBurstTick: LongWord;
    function BurstCount: LongWord;
    function NextBurst: Word;
    procedure ClearBurst;
    function GetSensorValue: LongWord;
    function GetSensorTick: LongWord;
    function NextSensorValue: Word;
    procedure ClearSensorValues;
    function SensorValuesCount: LongWord;
    procedure SetTimer(Interval: Word);
    procedure ClearTimer;
    procedure SaveFile;
    procedure CloseFile;
    procedure SaveBurstToFile;
    function SendProgramByte(B: Byte;Reverse: Byte): Word;
  End;

var
//  Interfaces: array[0..7] of TInterfaz;
  Interfaces: TObjectList;
  curInterfaz : integer;
  maxInterfaz : integer;
  BaudRate: integer;

implementation

procedure Delay(ticks: integer);
var
    t,u: integer;
begin
	t := GetTickCount;
    u := t;
    while u < t + ticks do u := GetTickCount;
end;

constructor TValue.Create(V:word);
begin
  Timestamp := Now();
  Tick := GetTickCount();
  Value := V;
end;

constructor TSensor.Create;
begin
  Burst := TObjectList.Create;
  Values := TObjectList.Create;
  Timer := TTimer.Create(Self);
end;

function TSensor.GetSensor: Word;
var
  Block: TIntList;
  ET : EventTimer;
  ok: Boolean;
begin
  ok := false;
  Block := TIntList.CreateEx;
  NewTimer(ET,5);  // OPTIMIZAR EL TIMER (18 = 1 seg)
//  Interfaz.Com.OnTriggerAvail := nil;
 // Interfaz.SensoresAskList.Add(Index);
  Value := 1024;
  Interfaz.Call;
  Interfaz.Com.PutChar(AnsiChar($20 + (Interfaz.curSensor shl 2)));
  while (not TimerExpired(ET)) and (Value = 1024) do
  	DelayTicks(1,True);

  if Value = 1024 then // REINTENTO
  begin
	  Interfaz.Call;
      Interfaz.Com.PutChar(AnsiChar($20 + (Interfaz.curSensor shl 2)));
      while (not TimerExpired(ET)) and (Value = 1024) do DelayTicks(1,True);
  end;

  Result := Value;

{

  // SACO EL DATO DEL PUERTO
  while (not TimerExpired(ET)) and (not ok) do
  begin
    Interfaz.Com.ProcessCommunications;
    while Interfaz.Com.CharReady do
    begin
      Block.Add(Ord(Interfaz.Com.GetChar));  // SON 4 Bytes: 0x55 0xFF SSH SSL
      ok := true;
    end;
  end;
  Interfaz.Com.OnTriggerAvail := Interfaz.OnTriggerAvail;
  // PROCESAR EL BLOQUE y DEVOLVER EL VALOR
  Result := $400;
  if not ok  then Result := $401;
  if ok and (Block.IntCount >= 4) then
  begin
    while Block.IntCount > 4 do Block.Delete(0);
    if (Block.Integers[0]=$55) and (Block.Integers[1]=$FF) and (Block.Integers[2]<>$AA) then
      Result := Block.Integers[2] * 256 + Block.Integers[3];
  end;
  }
end;

procedure TSensor.OnTimer(Sender: TObject);
var
  value : TValue;
  fdata: string;
begin
    value := TValue.Create(GetSensor);
    Values.Add(value);
    if DataFile <> '' then
    begin
      fdata := FormatDateTime('dd/mm/yyyy hh:nn:ss:z',value.Timestamp)
        +';'+IntToStr(value.Tick)+';'+IntToStr(value.Value);
     WriteLn(f,fdata);
    end;
end;


procedure TSensor.SetTimer(Interval: Word);
begin
    Timer.Interval := Interval;
    Timer.OnTimer := OnTimer;
    Timer.Enabled := true;
end;

procedure TSensor.ClearTimer;
begin
  Timer.Enabled := false;
end;

procedure TSensor.SaveToFile;
var
  s: TSaveDialog;
  i: integer;
  value: TValue;
  fdata: string;
begin
  s := TSaveDialog.Create(Self);
  s.Options := [ofOverwritePrompt,ofPathMustExist];
  s.DefaultExt := 'csv';
  s.Filter := 'Todos los archivos|*.*';
  if s.Execute then
  begin
    AssignFile(f,s.FileName);
    Rewrite(f);
    fdata := 'DATE;TICK;VALUE';
    WriteLn(f,fdata);
    while Burst.Count > 0 do
    begin
      value := (Burst[0] as TValue);
      fdata := FormatDateTime('dd/mm/yyyy hh:nn:ss:z',value.Timestamp)
        +';'+IntToStr(value.Tick)+';'+IntToStr(value.Value);
      WriteLn(f,fdata);
      Burst.Delete(0);
    end;
    CloseFile(f);
  end;
end;

procedure TInterfaz.OnTriggerAvail(CP: TObject; Count: Word);
var
   i,dataHigh,sensor, sensorValue, sensorIndex: integer;
   data: Byte;
   value : TValue;
   fdata: string;
   burstHigh, burstLow, header1, header2, headerout1, headerout2, sensorHigh: boolean;
begin
  // ANALIZE INPUT
   burstHigh := false;
   burstLow := false;
   sensor := 0;
   buffer:= TIntList.CreateEx;
   for i:=0 to Count -1 do
   begin
//        Application.ProcessMessages;
        data := Ord(Com.GetChar);
        if burstLow then
        begin
             value := TValue.Create(dataHigh * 256 + data);
             Sensores[sensor].Burst.Add(value);
             if Sensores[sensor].DataFile <> '' then
             begin
                fdata := FormatDateTime('dd/mm/yyyy hh:nn:ss:z',value.Timestamp)
                  +';'+IntToStr(value.Tick)+';'+IntToStr(value.Value);
               WriteLn(Sensores[sensor].f,fdata);
             end;
             burstLow := false;
        end;
        if burstHigh then
        begin
             dataHigh := data and 3;
             sensor := data shr 5;
             burstHigh := false;
             burstLow := true;
        end;
        if data = $0C then burstHigh := true;
        if sensorHigh then
        begin
            sensorValue := sensorValue * 256 + data;
            sensorHigh := false;
//            if SensoresAskList.IntCount > 0 then
//            begin
//            	Sensores[SensoresAskList.Integers[0]].Value := sensorValue;
//                SensoresAskList.Delete(0);
//            end;
            if (sensorIndex >= 0) and (sensorIndex <= 7) then
	           	Sensores[sensorIndex].Value := sensorValue;
        end;
    	if header2 then
        	if data <> $AA then
            begin
                sensorHigh := true;
                sensorValue := data;
                header2 := false;
            end;
        if header1 and (data = $FF) then
        begin
        	header2 := true;
            header1 := false;
        end;
        if data = $55 then header1 := true;
        if headerOut2 then
        	case data of
             $20: sensorIndex := 0;
             $24: sensorIndex := 1;
             $28: sensorIndex := 2;
             $2C: sensorIndex := 3;
             $30: sensorIndex := 4;
             $34: sensorIndex := 5;
             $38: sensorIndex := 6;
             $3C: sensorIndex := 7;
        end;
        if headerOut1 and (data = $FE) then
        begin
            headerOut1 := false;
            headerOut2 := true;
        end;
        if data = $54 then headerOut1 := true;

        // ANY OTHER OPERATION, CODE IT HERE
  // LOG EVERY INPUT IN BUFFER
    if Buffer.IntCount >= 65000 then Buffer.ClearEx(65000);
    Buffer.Add(data);
   end;
end;

constructor TInterfaz.Create(Port:integer);
var
  i: byte;
begin
    Com := TApdComPort.Create(Self);
    Buffer := TIntList.CreateEx;
    SensoresAskList := TIntList.CreateEx;
    for i := 0 to 7 do
    begin
        Sensores[i] := TSensor.Create;
        Sensores[i].Interfaz := Self;
        Sensores[i].Index := i;
    end;
    Com.ComNumber := Port;
    Com.Baud := BaudRate;
    Com.OnTriggerAvail := OnTriggerAvail;
    Com.Open := true;
end;

function TInterfaz.GetBufferFirst: word;
begin
  if Buffer.IntCount > 0 then
  begin
    Result := Buffer.Integers[0];
    Buffer.Delete(0);
  end else Result := 0;
end;

procedure TInterfaz.Close;
begin
  Com.Open := false;
  Free;
end;

procedure TInterfaz.Call;
begin
  Com.PutChar(AnsiChar($54));
  Com.PutChar(AnsiChar($FE));
end;

procedure TInterfaz.MotorOn;
begin
  Call;
  Com.PutChar(AnsiChar($40));
  Delay(1);
end;

procedure TInterfaz.MotorOff;
begin
  Call;
  Com.PutChar(AnsiChar($44));
  Delay(1);
end;

procedure TInterfaz.MotorInverse;
begin
  Call;
  Com.PutChar(AnsiChar($48));
  Delay(1);
end;

procedure TInterfaz.MotorThisWay;
begin
  Call;
  Com.PutChar(AnsiChar($4C));
  Delay(1);
end;

procedure TInterfaz.MotorThatWay;
begin
  Call;
  Com.PutChar(AnsiChar($50));
  Delay(1);
end;

procedure TInterfaz.MotorCoast;
begin
  Call;
  Com.PutChar(AnsiChar($54));
  Delay(1);
end;

procedure TInterfaz.MotorPower(Power:byte);
begin
  Call;
  Com.PutChar(AnsiChar($60));
  Com.PutChar(AnsiChar(Power));
  Delay(1);
end;

procedure TInterfaz.TalkMotor(Motors: Byte);
begin
  Call;
  Com.PutChar(AnsiChar($80));
  Com.PutChar(AnsiChar(Motors));  // One bit per Motor
  Delay(1);
end;

procedure TInterfaz.TalkPAP(PAP: byte);
begin
  Call;
  Com.PutChar(AnsiChar($80));
  Com.PutChar(AnsiChar($10 + $10 * PAP));  // PAP: 0 or 1
  Delay(1);
end;

procedure TInterfaz.PAPSpeed(Speed: byte);
begin
  Call;
  Com.PutChar(AnsiChar($64));
  Com.PutChar(AnsiChar(Speed));
  Delay(1);
end;

procedure TInterfaz.PAPSteps(Steps: Word);
begin
  Call;
  Com.PutChar(AnsiChar($C0 + Steps shr 8));
  Com.PutChar(AnsiChar(Steps and $FF));
  Delay(1);
end;

procedure TInterfaz.AllMotorsOff;
begin
  Call;
  Com.PutChar(AnsiChar($70));
  Com.PutChar(AnsiChar($00));
  Delay(1);
end;

procedure TInterfaz.ServoPos(Pos: Byte);
begin
  Call;
  Com.PutChar(AnsiChar($68));
  Com.PutChar(AnsiChar(Pos));
  Delay(1);
end;

procedure TInterfaz.TalkSensor(Sensor: Byte);
begin
  curSensor := Sensor;
end;

function TInterfaz.GetSensor: Word;
begin
  Result := Sensores[curSensor].GetSensor;
end;


function TInterfaz.GetSensorValue: LongWord;
begin
  if Sensores[curSensor].Values.Count > 0 then
    Result := (Sensores[curSensor].Values.Items[0] as TValue).Value
  else Result := $400;

end;

function TInterfaz.GetSensorTick: LongWord;
begin
  if Sensores[curSensor].Values.Count > 0 then
    Result := (Sensores[curSensor].Values.Items[0] as TValue).Tick;
end;

procedure TInterfaz.ClearSensorValues;
begin
  Sensores[curSensor].Values.Clear;
end;

function TInterfaz.NextSensorValue: Word;
begin
  if Sensores[curSensor].Values.Count > 0 then
  begin
      Sensores[curSensor].Values.Delete(0);
      Result := 1;
  end else Result := 0;
end;


procedure TInterfaz.SensorBurst(Sensors: Byte; Slow: Byte);
var
  i: integer;
  j: byte;
begin
  j := 1;
  for i := 0 to 7 do
  begin
    if (Sensors and j) > 0 then Sensores[i].Burst.Clear;
    j := j shl 1;
  end;
  Call;
  if Slow = 1 then Com.PutChar(AnsiChar($A1))
  else Com.PutChar(AnsiChar($A0));
  Com.PutChar(AnsiChar(Sensors));
  Delay(1);
end;

procedure TInterfaz.StopBurst;
begin
  Call;
  Com.PutChar(AnsiChar($A0));
  Com.PutChar(AnsiChar($00));
  Delay(1);
end;

function TInterfaz.BurstCount: LongWord;
begin
  if Sensores[curSensor].Burst.Count > 0 then
    Result := Sensores[curSensor].Burst.Count
  else Result := 0;
end;

function TInterfaz.GetBurstValue: Word;
begin
  if Sensores[curSensor].Burst.Count > 0 then
  begin
    Result := (Sensores[curSensor].Burst.First as TValue).Value;
  end
  else Result := $400;
end;

function TInterfaz.GetLastBurstValue: Word;
begin
  if Sensores[curSensor].Burst.Count > 0 then
  begin
    Result := (Sensores[curSensor].Burst.Last as TValue).Value;
  end
  else Result := $400;
end;

function TInterfaz.GetBurstTick: LongWord;
begin
  if Sensores[curSensor].Burst.Count > 0 then
    Result := (Sensores[curSensor].Burst.Items[0] as TValue).Tick
  else Result := 0;
end;

function TInterfaz.NextBurst: Word;
begin
  if Sensores[curSensor].Burst.Count > 0 then
  begin
    Sensores[curSensor].Burst.Delete(0);
    if Sensores[curSensor].Burst.Count > 0 then
      Result := 1
    else Result := 0;
  end else
    Result := 0;
end;

procedure TInterfaz.ClearBurst;
begin
  Sensores[curSensor].Burst.Clear;
end;

function TInterfaz.SensorValuesCount: LongWord;
begin
  Result := Sensores[curSensor].Values.Count;
end;

procedure TInterfaz.SetTimer(Interval: Word);
begin
  Sensores[curSensor].SetTimer(Interval);
end;

procedure TInterfaz.ClearTimer;
begin
  Sensores[curSensor].ClearTimer;
end;

procedure TInterfaz.SaveFile;
var
  s: TSaveDialog;
begin
  s := TSaveDialog.Create(Self);
  s.Options := [ofOverwritePrompt,ofPathMustExist];
  s.DefaultExt := 'csv';
  s.Filter := 'Todos los archivos|*.*';
  if s.Execute then
  begin
    if Sensores[curSensor].DataFile <> '' then
      CloseFile;
    Sensores[curSensor].DataFile := s.FileName;
    AssignFile(Sensores[curSensor].f,s.FileName);
    Rewrite(Sensores[curSensor].f);
  end;
end;

procedure TInterfaz.CloseFile;
begin
  System.CloseFile(Sensores[curSensor].f);
  Sensores[curSensor].DataFile := '';
end;

procedure TInterfaz.SaveBurstToFile;
begin
  Sensores[curSensor].SaveToFile;
end;

function TInterfaz.SendProgramByte(B: Byte; Reverse: Byte): Word;
var
	ET: EventTimer;
    c,r: AnsiChar;
begin
  Com.OnTriggerAvail := nil;
  Com.PutChar(AnsiChar(B));
  DelayTicks(1,False);
{
  NewTimer(ET,10);  // OPTIMIZAR EL TIMER (18 = 1 seg)
//	DelayTicks(1,False);
  while (not TimerExpired(ET)) do
  	if Com.CharReady then
    begin
    	c := Com.GetChar;
        if Reverse = 1 then
        	if Com.CharReady then r := Com.GetChar;
        break;
    end;
  Com.OnTriggerAvail := OnTriggerAvail;
  if StrToInt(c) = B then Result := 1
  else Result := 0;
  if Reverse = 1 then
  	if (Result = 1) and (r = AnsiChar(not B)) then Result := 1
  	else Result := 0;
  }
  Result := 1;
end;


end.
